package com.github.houbb.spi.api.impl;

import com.github.houbb.heaven.annotation.ThreadSafe;
import com.github.houbb.heaven.util.common.ArgUtil;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.net.URLUtil;
import com.github.houbb.heaven.util.util.CollectionUtil;
import com.github.houbb.spi.annotation.SPI;
import com.github.houbb.spi.api.IExtensionLoader;
import com.github.houbb.spi.constant.SpiConst;
import com.github.houbb.spi.exception.SpiException;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 默认实现
 * @author binbin.hou
 * @since 0.0.1
 */
@ThreadSafe
public class ExtensionLoader<T> implements IExtensionLoader<T> {

    /**
     * 接口定义
     * @since 0.0.1
     */
    private final Class<T> spiClass;

    /**
     * 类加载器
     * @since 0.0.1
     */
    private final ClassLoader classLoader;

    /**
     * 缓存的对象实例
     * @since 0.0.1
     */
    private final Map<String, T> cachedInstances = new ConcurrentHashMap<>();

    /**
     * 实例别名 map
     * @since 0.0.1
     */
    private final Map<String, String> classAliasMap = new ConcurrentHashMap<>();

    public ExtensionLoader(Class<T> spiClass, ClassLoader classLoader) {
        spiClassCheck(spiClass);
        ArgUtil.notNull(classLoader, "classLoader");

        this.spiClass = spiClass;
        this.classLoader = classLoader;

        // 初始化配置
        this.initSpiConfig();
    }

    public ExtensionLoader(Class<T> spiClass) {
        this(spiClass, Thread.currentThread().getContextClassLoader());
    }

    /**
     * 获取对应的拓展信息
     *
     * @param alias 别名
     * @return 结果
     * @since 0.0.1
     */
    @Override
    public T getExtension(String alias) {
        ArgUtil.notEmpty(alias, "alias");

        //1. 获取
        T instance = cachedInstances.get(alias);
        if(instance != null) {
            return instance;
        }

        // DLC
        synchronized (cachedInstances) {
            instance = cachedInstances.get(alias);
            if(instance == null) {
                instance = createInstance(alias);
                cachedInstances.put(alias, instance);
            }
        }

        return instance;
    }

    /**
     * 实例
     * @param name 名称
     * @return 实例
     * @since 0.0.1
     */
    @SuppressWarnings("unchecked")
    private T createInstance(String name) {
        String className = classAliasMap.get(name);
        if(StringUtil.isEmpty(className)) {
            throw new SpiException("SPI config not found for spi: " + spiClass.getName()
                    +" with alias: " + name);
        }

        try {
            Class clazz = Class.forName(className);
            return (T) clazz.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            throw new SpiException(e);
        }
    }

    /**
     * 参数校验
     *
     * 1. 不能为 null
     * 2. 必须是接口
     * 3. 必须指定 {@link com.github.houbb.spi.annotation.SPI} 注解
     * @param spiClass spi 类
     * @since 0.0.1
     */
    private void spiClassCheck(final Class<T> spiClass) {
        ArgUtil.notNull(spiClass, "spiClass");

        if(!spiClass.isInterface()) {
            throw new SpiException("Spi class is not interface, " + spiClass);
        }
        if(!spiClass.isAnnotationPresent(SPI.class)) {
            throw new SpiException("Spi class is must be annotated with @SPI, " + spiClass);
        }
    }

    /**
     * 初始化配置文件名称信息
     *
     * 只加载当前类的文件信息
     * @since 0.0.1
     */
    private void initSpiConfig() {
        // 文件的读取
        String fullName = SpiConst.JDK_DIR+this.spiClass.getName();

        try {
            Enumeration<URL> urlEnumeration = this.classLoader
                    .getResources(fullName);

            // 没有更多元素
            if(!urlEnumeration.hasMoreElements()) {
                throw new SpiException("SPI config file for class not found: "
                        + spiClass.getName());
            }

            // 获取第一个元素
            URL url = urlEnumeration.nextElement();
            List<String> allLines = URLUtil.readAllLines(url);

            // 构建 map
            if(CollectionUtil.isEmpty(allLines)) {
                throw new SpiException("SPI config file for class is empty: " + spiClass.getName());
            }

            for(String line : allLines) {
                String[] lines = line.split(SpiConst.SPLITTER);
                classAliasMap.put(lines[0], lines[1]);
            }
        } catch (IOException e) {
            throw new SpiException(e);
        }
    }

}
